package com.bestcem.xm.ticket.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.ObjectId;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.bestcem.xm.common.core.domain.web.ServiceResult;
import com.bestcem.xm.common.core.enums.ReqErrorCodeEnum;
import com.bestcem.xm.common.core.enums.ResourceErrorCodeEnum;
import com.bestcem.xm.ticket.constant.Constants;
import com.bestcem.xm.ticket.controller.convert.wechat.TicketWechatConvert;
import com.bestcem.xm.ticket.controller.param.NotificationParam;
import com.bestcem.xm.ticket.controller.vo.NotificationVO;
import com.bestcem.xm.ticket.controller.vo.SimpleIdVO;
import com.bestcem.xm.ticket.controller.vo.wechat.WechatUserInfoVO;
import com.bestcem.xm.ticket.dao.NotificationDao;
import com.bestcem.xm.ticket.dao.TicketWechatDao;
import com.bestcem.xm.ticket.dao.TicketWechatNotificationDao;
import com.bestcem.xm.ticket.entity.mongo.Notification;
import com.bestcem.xm.ticket.entity.mysql.TicketWechatDO;
import com.bestcem.xm.ticket.enums.NotificationTypeEnum;
import com.bestcem.xm.ticket.grpc.client.TicketBaseQdesGrpcService;
import com.bestcem.xm.ticket.grpc.client.TicketBaseUserGrpcService;
import com.bestcem.xm.ticket.grpc.client.TicketWechatGrpcService;
import com.bestcem.xm.ticket.grpc.client.dto.qdes.QdesDTO;
import com.bestcem.xm.ticket.grpc.client.dto.user.UserDTO;
import com.bestcem.xm.ticket.grpc.client.dto.wechat.WechatUserInfoDTO;
import com.bestcem.xm.ticket.service.TicketNotificationService;
import com.bestcem.xm.ticket.service.convert.TicketNotificationMapper;
import com.bestcem.xm.ticket.service.dto.mq.NotificationTriggerDTO;
import com.bestcem.xm.ticket.util.NotificationUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
 * 记录问卷通知的各类提醒方式
 *
 * @author jy.zhao
 * @version 通知提醒新增微信公众号
 * @date 2021/8/23 15:03
 * @date 2022/4/13 17:03 XiaoYunSong 通知提醒新增微信公众号需求 新增相关方法
 */
@Slf4j
@Service
public class TicketNotificationServiceImpl implements TicketNotificationService {

    @Resource
    private NotificationUtil notificationUtil;

    @Resource
    private NotificationDao notificationDao;
    @Resource
    private TicketWechatDao ticketWechatDao;
    @Resource
    private TicketWechatNotificationDao ticketWechatNotificationDao;

    @Resource
    private TicketNotificationMapper ticketNotificationMapper;

    @Resource
    private TicketBaseQdesGrpcService ticketBaseQdesGrpcService;
    @Resource
    private TicketBaseUserGrpcService ticketBaseUserGrpcService;
    @Resource
    private TicketWechatGrpcService ticketWechatGrpcService;

    @Value("${app.ticket.notification.subscribe-limit:5}")
    Integer notificationSubscribeLimit;

    /**
     * 用问卷Id得到TicketNotification，没有则创建
     *
     * @param surveyId
     * @return 详细信息
     * @author zq.ding <zq.ding@idiaoyan.com>
     * @date 2021/8/23 14:19
     * @date 2022/4/7 12:16 XiaoYunSong 通知提醒新增微信公众号需求 新增 是否开启免打扰/是否开启微信公众号通知
     */
    @Override
    public ServiceResult<NotificationVO> getNotificationBySurveyId(String surveyId) {
        if (checkParam(surveyId)) {
            return ServiceResult.failWithCode(ResourceErrorCodeEnum.NOT_FOUND, "project " + surveyId + " not found");
        }

        Notification entity = notificationDao.findBySurveyId(surveyId);
        if (Objects.isNull(entity)) {
            entity = createDefaultNotification(surveyId);
        }

        // 处理历史数据
        if (Objects.nonNull(entity.getWays()) && Objects.isNull(entity.getWays().getWechatPublicEnable())) {
            entity.getWays().setWechatPublicEnable(true);
            notificationDao.updateNotificationBySurveyId(entity);
        }

        // 如果Notification表中rule对象为空,则需要初始化一下再返回给前端
        NotificationVO isVo = ticketNotificationMapper.entity2Vo(entity);
        if (Objects.isNull(isVo.getRule())) {
            NotificationVO.Rule rule = new NotificationVO.Rule();
            isVo.setRule(rule);
        }
        // 初始化
        if (CollectionUtil.isEmpty(isVo.getNoDisturb())
                || StrUtil.isEmpty(isVo.getNoDisturb().get(0))) {
            isVo.setNoDisturb(Arrays.asList("01:00-05:00"));
        }
        // 通知提醒新增微信公众号需求 新增 免打扰开关字段, 默认为 true
        if (Objects.isNull(isVo.getNoDisturbEnable())) {
            isVo.setNoDisturbEnable(true);
        }
        return ServiceResult.success(isVo);
    }

    /**
     * 检查问卷项目是否存在
     *
     * @param surveyId 问卷id
     * @return
     */
    private boolean checkParam(String surveyId) {
        QdesDTO qdesProject = ticketBaseQdesGrpcService.getQdesProject(surveyId);
        return Objects.isNull(qdesProject);
    }

    /**
     * 根据传入的surveyId和param更新notification
     *
     * @param surveyId
     * @return id
     * @author zq.ding <zq.ding@idiaoyan.com>
     * @date 2021/8/23 17:38
     * @date 2022/4/7 12:16 XiaoYunSong 通知提醒新增微信公众号需求 新增 是否开启免打扰/是否开启微信公众号通知
     */
    @Override
    public ServiceResult<SimpleIdVO> updateNotification(String surveyId, NotificationParam param) {
        if (checkParam(surveyId)) {
            return ServiceResult.failWithCode(ResourceErrorCodeEnum.NOT_FOUND, "project " + surveyId + " not found");
        }

        Long result = notificationUtil.updateNotification(surveyId, param);
        if (ObjectUtil.isNotNull(result) && result == 1) {
            return ServiceResult.success(new SimpleIdVO(surveyId));
        }
        return ServiceResult.failWithCode(ResourceErrorCodeEnum.WRITE_FAIL, "更新失败");
    }

    /**
     * 创建默认的通知提醒
     *
     * @param projectId 问卷id
     * @author jy.zhao
     * @date 2021/8/23 16:32
     * @date 2022/4/7 12:16 XiaoYunSong 通知提醒新增微信公众号需求 新增 是否开启免打扰/是否开启微信公众号通知
     */
    @NotNull
    @Override
    public Notification createDefaultNotification(String projectId) {
        if (!ObjectId.isValid(projectId)) {
            log.error("[ticket] createNotification projectId 错误");
            return new Notification();
        }
        // 根据projectId查询qdes_get_project
        QdesDTO projectDTO = ticketBaseQdesGrpcService.getQdesProject(projectId);
        if (Objects.isNull(projectDTO)) {
            // python这里是抛异常,这里即使不抛异常也会直接返回
            log.error("[Ticket] Ticket qdes.Project[id={}] not found", projectId);
            return new Notification();
        }
        UserDTO userDTO = null;
        if (StringUtils.isNotEmpty(projectDTO.getCreator())) {
            if (ObjectId.isValid(projectDTO.getCreator())) {
                // 获取项目管理员
                userDTO = ticketBaseUserGrpcService.getUser(projectDTO.getCreator());
            }
        }

        if (Objects.isNull(userDTO)) {
            userDTO = new UserDTO();
        }

        Notification entity = notificationDao.findBySurveyId(projectId);
        // 本方法为创建新的notification,当notification存在时直接返回
        if (Objects.nonNull(entity)) {
            return entity;
        }
        // 通知方式设置
        entity = new Notification();
        Notification.Way way = new Notification.Way();
        if (StringUtils.isNotEmpty(userDTO.getMobile())) {
            way.setMobiles(Arrays.asList(userDTO.getMobile()));
        }
        way.setMobileEnable(false);
        if (StringUtils.isNotEmpty(userDTO.getEmail())) {
            way.setEmails(Arrays.asList(userDTO.getEmail()));
        }
        // 根据是否有邮箱判断是否要默认勾选邮件通知
        if (StringUtils.isNotEmpty(userDTO.getEmail())) {
            way.setEmailEnable(true);
        }
        // 默认开启公众号通知
        way.setWechatPublicEnable(true);
        entity.setWays(way);
        entity.setNoDisturb(Arrays.asList(Constants.TICKET_NO_DISTURB_TIME_RANGE));
        entity.setNoDisturbEnable(true);
        entity.setSurveyId(projectDTO.getId());
        entity.setOrgId(projectDTO.getCompanyId());
        // 新建notification规则均为空
        entity.setRule(new Notification.Rule());
        return notificationDao.saveNotification(entity);
    }

    @Override
    public void triggerNotification(NotificationTriggerDTO triggerDTO) {
        if (log.isInfoEnabled()) {
            log.info("[ticket] 触发通知,参数：{} start", triggerDTO);
        }
        Integer noticeType = triggerDTO.getNoticeType();
        if (Validator.isNull(noticeType)) {
            log.error("[ticket] 触发通知 没有传递通知类型。传递参数={}", triggerDTO);
            return;
        }
        // 根据项目id查询通知提醒方式
        Notification notification = notificationDao.findBySurveyId(triggerDTO.getProjectId());
        if (Validator.isNull(notification)) {
            log.error("[ticket] 触发通知 没有找到surveyId={}对应的通知方式", triggerDTO.getProjectId());
            return;
        }
        // 检查是否开启了对应的通知提醒
        if (!NotificationUtil.validNotificationIsEnable(noticeType, notification)) {
            return;
        }
        Integer rspdCount = triggerDTO.getRspdCount();
        if (Validator.isNull(rspdCount)) {
            rspdCount = 0;
        }
        // 检查是否开启每次答卷完成提醒
        if (NotificationTypeEnum.EVERY_RSPD.getIndex().equals(noticeType) && rspdCount == 1) {
            return;
        }
        Integer reachCount = notification.getRule().getReachCount();
        // 检查答卷数据是否达到指定数量
        boolean valid = NotificationTypeEnum.REACH_RSPD.getIndex().equals(noticeType) && (Validator.isNull(reachCount) || !rspdCount.equals(reachCount));
        if (valid) {
            return;
        }
        // 调用notification_utils.NotificationHelper
        // @赵金毅: 此处调用时python原方法需要notifcation、n_type_tag、n_type、count四个参数 n_type_tag已添加值从哪里取
        notificationUtil.notifyHelper(notification, null, noticeType, triggerDTO.getTypeTag(), triggerDTO.getSeq());
        if (log.isInfoEnabled()) {
            log.info("[ticket] 触发通知 end");
        }
    }


    /**
     * 获取通知提醒对应的微信订阅用户
     *
     * @param surveyId 问卷id
     * @return 微信用户列表
     * @author XiaoYunSong <ys.xiao@idiaoyan.com>
     * @date 2022/4/7 10:49
     */
    @Override
    public ServiceResult<List<WechatUserInfoVO>> getNotificationSubscribers(String surveyId) {
        if (!ObjectId.isValid(surveyId)) {
            return ServiceResult.fail(ReqErrorCodeEnum.PARAM_FORMAT, "问卷id格式错误");
        }

        List<WechatUserInfoVO> result = new ArrayList<>();
        List<String> subscriberOpenIds = ticketWechatNotificationDao.listNotificationSubscriberOpenIds(surveyId);
        if (CollectionUtils.isEmpty(subscriberOpenIds)) {
            return ServiceResult.success(result);
        }
        List<TicketWechatDO> ticketWechatDoS = ticketWechatDao.listByOpenIds(subscriberOpenIds);
        if (!(CollectionUtils.isEmpty(ticketWechatDoS))) {
            for (TicketWechatDO ticketWechatDo : ticketWechatDoS) {
                WechatUserInfoVO wechatUserInfoVO = TicketWechatConvert.TicketWechatDOToVO(ticketWechatDo);
                if (Objects.nonNull(wechatUserInfoVO)) {
                    result.add(wechatUserInfoVO);
                }
            }
        }
        return ServiceResult.success(result);
    }

    /**
     * 新增通知提醒对应的微信订阅用户
     *
     * @param surveyId 问卷id
     * @param openId   微信openId
     * @author XiaoYunSong <ys.xiao@idiaoyan.com>
     * @date 2022/4/7 13:09
     */
    @Override
    public ServiceResult<Boolean> createNotificationSubscriber(String surveyId, String openId) {
        TicketWechatDO isDo = ticketWechatDao.findByOpenId(openId);
        if (Objects.isNull(isDo)) {
            WechatUserInfoDTO wechatUserInfo = ticketWechatGrpcService.getWechatUserInfo(openId);
            isDo = TicketWechatConvert.WechatUserInfoDTOToDO(wechatUserInfo);
            // python 那边是存在缓存里, 可能会过期
            if (Objects.isNull(isDo)) {
                return ServiceResult.fail(ResourceErrorCodeEnum.NOT_FOUND, "微信授权信息已过期");
            }
            isDo.setOpenId(openId);
            // 记录微信用户详细信息
            ticketWechatDao.save(isDo);
        }
        isDo = ticketWechatDao.findByOpenId(openId);
        if (Objects.isNull(isDo)) {
            return ServiceResult.fail(ResourceErrorCodeEnum.TOO_BUSY, "订阅失败");
        }
        if (Objects.nonNull(ticketWechatNotificationDao.findNotificationSubscriber(surveyId, openId))) {
            return ServiceResult.fail(ResourceErrorCodeEnum.FOUND, "用户已订阅该问卷");
        }

        // 订阅人数限制
        List<String> subscribers = ticketWechatNotificationDao.listNotificationSubscriberOpenIds(surveyId);
        if (!CollectionUtils.isEmpty(subscribers) && subscribers.size() >= notificationSubscribeLimit) {
            return ServiceResult.fail(ResourceErrorCodeEnum.TOO_MUCH, "订阅人数达到上限");
        }
        // 添加订阅关系
        boolean result = ticketWechatNotificationDao.saveNotificationSubscription(surveyId, openId);
        if (!result) {
            return ServiceResult.fail(ResourceErrorCodeEnum.TOO_BUSY, "订阅失败");
        }
        return ServiceResult.success(true);
    }


    /**
     * 移除通知提醒微信订阅用户信息
     *
     * @param surveyId      问卷项目id
     * @param wechatOpenIds 需要移除订阅的微信openId列表
     * @return 成功移除的微信用户的openId列表
     * @author XiaoYunSong <ys.xiao@idiaoyan.com>
     * @date 2022/4/7 11:30
     */
    @Override
    public ServiceResult<Object> removeNotificationSubscribers(String surveyId, List<String> wechatOpenIds) {
        if (!ObjectId.isValid(surveyId)) {
            return ServiceResult.fail(ReqErrorCodeEnum.PARAM_FORMAT, "问卷id格式错误");
        }
        if (CollectionUtils.isEmpty(wechatOpenIds)) {
            return ServiceResult.success();
        }

        Boolean result = ticketWechatNotificationDao.removeNotificationSubscription(surveyId, wechatOpenIds);
        if (!result) {
            return ServiceResult.fail(ResourceErrorCodeEnum.NOT_FOUND, "删除失败");
        }
        return ServiceResult.success();
    }


    /**
     * 移除微信用户订阅的所有问卷
     *
     * @param openId 微信openId
     * @return void
     * @author XiaoYunSong <ys.xiao@idiaoyan.com>
     * @date 2022/4/11 14:50
     */
    @Override
    public ServiceResult<Object> removeWechatUserSubscription(String openId) {
        Boolean result = ticketWechatNotificationDao.removeWechatUserSubscription(openId);
        if (!result) {
            return ServiceResult.fail(ResourceErrorCodeEnum.NOT_FOUND, "删除失败");
        }
        return ServiceResult.success();
    }
}
